////////////////////////////////////////////////////////////////////
// File:	DecalManager.cpp
// Purpose: Manages all decals in game and uses a ring buffer to reuse
//			any decals so that we don't lag the game out because of
//			too many decals in game. You can create decals on the fly,
//			but remember decals will be reused and you cannot exceed
//			the capacity defined "MAX_DECALS."
// Author:	Jay Baird
////////////////////////////////////////////////////////////////////

#include "DecalManager.h"
#include "MeshManager.h"
#include "..\..\src\Game Objects\ObjectManager.h"
#include <glee.h>
#include "..\GLRendererWin32.h"
#include "..\..\src\Graphics.h"
#include <stack>

using namespace Collision;

// Static Variables:
DecalManager DecalManager::m_instance;

DecalManager::DecalManager() : m_currentDecal(0), m_numDecalsUsed(0)
{

}

void DecalManager::Initialize()
{
	// Setup for instancing/reusing
	m_decals.clear();
	m_targets.clear();
	m_renderables.clear();
	m_decals.resize(MAX_DECALS);

	m_numDecalsUsed = m_currentDecal = 0;

	unsigned numDecals = (unsigned)m_decals.size();
	for (unsigned i = 0; i < numDecals; i++)
		m_decals[i].SetVisible(false);
}

void DecalManager::Shutdown()
{
	m_decals.clear();
	m_renderables.clear();
	m_targets.clear();

	// Re init everything to zero it out
	m_numDecalsUsed = m_currentDecal = 0;
}

void DecalManager::UpdateDecals(const float fTimeStep)
{
	// Updating decals
	// TODO: (JB)
	for (unsigned i = 0; i < m_numDecalsUsed; i++)
	{
		if(m_decals[i].IsVisible())
		{
			m_decals[i].Update(fTimeStep);

			if(m_decals[i].GetColor().IsInvisible())
				RemoveTarget(&m_decals[i]);
		}
	}
}

void DecalManager::RenderDecals() const
{
	if(m_numDecalsUsed == 0) return;

	Graphics& graphics = Graphics::Instance();

	std::vector<IRenderable*> targetList;
	ConvertToIRenderable(m_targets, targetList);

	glPushAttrib(GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_ENABLE_BIT);
	{
		glDepthMask(GL_FALSE);
		glDisable(GL_DEPTH_TEST);

		glEnable(GL_STENCIL_TEST);
		glClearStencil(0);

		// Draw blocks w/ color turned off
		glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
		glStencilFunc(GL_ALWAYS, 1, 1);
		glStencilOp(GL_KEEP, GL_ZERO, GL_REPLACE);

		graphics.SetList(&targetList);
		graphics.Flush(Graphics::DRAW_3D);

		// Draw Decals
		glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
		glStencilFunc(GL_EQUAL, 1, 1);
		glStencilMask(GL_FALSE);

		graphics.SetList(&m_renderables);
		graphics.Flush(Graphics::DRAW_3D);
	}
	glPopAttrib();
}

void DecalManager::CreateDecal(BaseObject* pTarget, const char* textureFile, const vec3& center, 
							   PlaneType planeType, float width, float height, PaintType paintType)
{
	// No target? Don't create a decal
	if(!pTarget) return;

	// Yaw:		Y Axis (turning)
	// Pitch:	X Axis (spinning)
	// Roll:	Z Axis (rolling)

	MeshManager* pMM = MeshManager::GetInstance();

	// Reset to the beginning
	if(m_currentDecal + 1 >= MAX_DECALS)
		m_currentDecal = 0;

	// Reset it
	m_decals[m_currentDecal] = Decal();
	m_decals[m_currentDecal].SetTarget(pTarget);

	// Since a quad is drawn from the center.. split the width and height
	width *= 0.5f;
	height *= 0.5f;

	// Setup collision volume now & other attributes
	m_decals[m_currentDecal].GetAABB().SetMin(vec3(center.X() - width, center.Y() - height, 0.0f));
	m_decals[m_currentDecal].GetAABB().SetMax(vec3(center.X() + width, center.Y() + height, 0.0f));
	m_decals[m_currentDecal].GetLocalMatrix().MakeTranslation(center);

	// Setup decal paint type and switch to determine color
	m_decals[m_currentDecal].SetPaintType(paintType);
	m_decals[m_currentDecal].SetColor(paintType);

	// Determine the plane attributes specific to the plane they want to create this decal on
	float yaw = 0.0f, pitch = 0.0f, roll = 0.0f;
	switch(planeType)
	{
	case LEFT:
		yaw = 90.0f;
		break;
	case RIGHT:
		yaw = -90.0f;
		break;
	case TOP:
		roll = 90.0f;
		break;
	case BOTTOM:
		roll = -90.0f;
		break;
	};

	// Convert to radians
	yaw *= DegreesToRad;
	roll *= DegreesToRad;
	pitch = RAND_FLOAT(0.0f, 360.0f) * DegreesToRad;

	// Default random pitch (rotX)
	m_decals[m_currentDecal].GetLocalMatrix().RotPreX(roll);
	m_decals[m_currentDecal].GetLocalMatrix().RotPreY(yaw);
	m_decals[m_currentDecal].GetLocalMatrix().RotPreZ(pitch);

	m_decals[m_currentDecal].GetLocalMatrix().ScalePre(width, height, 1.0f);
	m_decals[m_currentDecal].SetMeshID(pMM->LoadQuad(textureFile));
	m_decals[m_currentDecal].SetVisible(true);

	if(m_numDecalsUsed < MAX_DECALS)
	{
		// Push back into renderable list
		m_renderables.push_back(&m_decals[m_currentDecal]);
		m_currentDecals.push_back(&m_decals[m_currentDecal]);

		m_numDecalsUsed++;
	}
	else 
	{
		// Remove old target
		RemoveTarget(&m_decals[m_currentDecal]);

		m_renderables[m_currentDecal] = &m_decals[m_currentDecal];
		m_currentDecals[m_currentDecal] = &m_decals[m_currentDecal];
	}

	// Add new target
	AddTarget(&m_decals[m_currentDecal]);

	m_currentDecal++;	// Increment the current decal
}

void DecalManager::AddTarget(Decal* pDecal)
{
	BaseObject* pTarget = pDecal->GetTarget();

	unsigned targetCount = (unsigned)m_targets.size();
	unsigned i = 0;
	for (; i < targetCount; i++)
		if(pTarget == m_targets[i]) break;

	if(i == targetCount)
		m_targets.push_back(pTarget);
}

void DecalManager::RemoveTarget(Decal* pDecal)
{
	unsigned numDupes = 0;

	std::vector<Decal>::iterator iter = m_decals.begin();
	for (; iter != m_decals.end(); iter++)
	{
		if(pDecal->GetTarget() == (*iter).GetTarget())
			numDupes++;
	}

	// Remove target
	if(numDupes == 1)
	{
		std::vector<BaseObject*>::iterator targetIter = m_targets.begin();
		for(; targetIter != m_targets.end(); targetIter++)
		{
			if(pDecal->GetTarget() == (*targetIter))
			{
				m_targets.erase(targetIter);
				break;
			}
		}
	}
}